package carte;

import java.awt.Dimension;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Random;

import view.colorant.Colorant;
import carte.element.Water;
import carte.terrain.Forest;
import carte.terrain.Land;

public class Carte implements Cloneable {

	private int width;
	private int height;

	private Cellule[][] map;

	// altitude maximum a la generation de la carte
	public static final int MAX_ALTITUDE = 100;
	// altitude de la mer a la generation de la carte
	public static final int SEA_ALTITUDE = 35;

	/* ************************** */

	public Carte() {
		this(20);
	}

	public Carte(int width) {
		this(width, width);
	}

	public Carte(int width, int height) {
		this.width = width;
		this.height = height;

		map = new Cellule[width][height];

		initMap();
	}

	private void initMap() {
		Random rand = new Random();
		PerlinNoise noiseAltitude = new PerlinNoise(width, MAX_ALTITUDE);
		int land = 0;
		int altitude;
		int amax = 0;
		int amin = MAX_ALTITUDE;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				altitude = noiseAltitude.PerlinNoise(i, j);
				// on regarde si le terrain peut bien etre positionne a
				// cette altitude sinon on prend le suivant
				do {
					// sans la foret
					land = rand.nextInt(Land.lands.length - 1);
				} while (Land.lands[land].getAltitudeMin() > altitude
						|| Land.lands[land].getAltitudeMax() < altitude);
				map[i][j] = new Cellule(altitude, Land.lands[land]);
				
				/*
				 * A present, si on est sous le niveau de la mer, on va rajouter
				 * a la case de l'eau correspondant à la difference entre le
				 * niveau de la mer et l'altitude de la case
				 */
				if (altitude < SEA_ALTITUDE) {
					map[i][j].createFluid(Water.getTypeName(), SEA_ALTITUDE
							- altitude);
				}
			}
		}
		//maintenant on va generer des foret !!
		Forest.generateForest(this);
	}

	public Cellule getCell(int i, int j) {
		return map[i][j];
	}

	/*
	 * renvoie une arraylist des cellules environnant la case, si elles existent
	 * (meme en diag) Le premier élément de l'arraylist est toujours la case
	 * elle-même
	 */
	public ArrayList<Cellule> getCellAround(int i, int j) {
		ArrayList<Cellule> cells = new ArrayList<Cellule>(0);
		// on commence par ajouter la case elle-même
		cells.add(getCell(i, j));
		for (int x = -1; x <= 1; x++) {
			for (int y = -1; y <= 1; y++) {
				// on vérifie que la case existe
				if ((x + i >= 0) && (y + j >= 0) && (x + i < width)
						&& (y + j < height)) {
					// on vérifie que ce n'est pas la case 0 : 0
					if (x != 0 || y != 0) {
						cells.add(getCell(x + i, y + j));
					}
				}
			}
		}
		return cells;
	}

	/*
	 * Renvoie une arraylist avec les cases à une certaine distance
	 * On doit définir la portée du rayon 1 donne les cases
	 * immédiatement adjacentes (meme en diag) 2 donne les cases à 2 de portee max
	 * En cercle
	 * ...
	 */
	public ArrayList<Cellule> getCellRange(int i, int j, int range) {
		ArrayList<Cellule> cells = new ArrayList<Cellule>();
		// on commence par ajouter la case elle-même
		cells.add(getCell(i, j));
		for (int x = -range; x <= range; x++) {
			for (int y = -range; y <= range; y++) {
				// on vérifie que la case existe
				if ((x + i >= 0) && (y + j >= 0) && (x + i < width)
						&& (y + j < height)) {
					// on vérifie que ce n'est pas la case 0 : 0
					if (x != 0 || y != 0) {
						// on vérifie que la case est bien dans la portee de la case
						// centrale
						if (!isOutOfRange(x, y, range)) {
							cells.add(getCell(x + i, y + j));
						}
					}
				}
			}
		}
		return cells;
	}

	// renvoie true si la case est hors de portee (pas de diagonales)
	// x et y coordonnées de la case : la case centrale est a la coordonne 0,0
	private boolean isOutOfRange(int x, int y, int rangeMax) {
		double range = 0;
		range = Math.sqrt( (double)(Math.abs(x) * Math.abs(x)) + (double)(Math.abs(y) * Math.abs(y)) );
		if (range > (double)rangeMax) {
			return true;
		} else {
			return false;
		}
	}

	public Dimension getDimensions() {
		return new Dimension(width, height);
	}

	public Object clone() {
		Carte carte = null;
		try {
			// On récupère l'instance à renvoyer par l'appel de la
			// méthode super.clone()
			carte = (Carte) super.clone();
		} catch (CloneNotSupportedException cnse) {
			// Ne devrait jamais arriver car nous implémentons
			// l'interface Cloneable
			cnse.printStackTrace(System.err);
		}

		carte.map = new Cellule[width][height];
		// On clone l'attribut de type terrain qui n'est pas immuable
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				carte.map[i][j] = (Cellule) map[i][j].clone();
			}
		}

		// on renvoie le clone
		return carte;
	}

	public Cellule[][] getMap() {
		return map;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}
}
